﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using System.Xml.Serialization;
using Kristall.MyGame.Enemies;
using Kristall.MyGame.Scripts;

namespace Kristall.Engine
{
    [Serializable]
    public abstract class Actor : Animatable
    {
        // Constants for controlling horizontal movement
        [XmlIgnore]
        float _moveAcceleration = 12000f;

        [XmlIgnore]
        public virtual float MoveAcceleration
        {
            get
            {
                return _moveAcceleration;
            }
            set
            {
                _moveAcceleration = value;
            }
        }

        [XmlIgnore]
        public virtual float MaxMoveSpeed
        {
            get
            {
                return 1750.0f;
            }
        }

        [XmlIgnore]
        public virtual float GroundDragFactor
        {
            get
            {
                return 0.48f;
            }
        }

        float _stunTime = 0.1f;
        public virtual float StunTime
        {
            get
            {
                return _stunTime;
            }
            set
            {
                _stunTime = value;
            }
        }

        public float StunTimer;

        public float StunBlinkRate
        {
            get;
            set;
        }

        [XmlIgnore]
        private float AirDragFactor { get { return Kristall.Options.air_drag; } }

        // Constants for controlling vertical movement
        [XmlIgnore]
        public virtual float MaxJumpTime
        {
            get
            {
                return 0.35f;
            }
        }

        [XmlIgnore]
        public virtual float JumpLaunchVelocity
        {
            get
            {
                return -3500.0f;
            }
        }

        [XmlIgnore]
        float _gravityAcceleration = 3400f;

        [XmlIgnore]
        public float GravityAcceleration
        {
            get
            {
                return _gravityAcceleration;
            }
            set
            {
                _gravityAcceleration = value;
            }
        }

        [XmlIgnore]
        public float MaxFallSpeed
        {
            get
            {
                return 550.0f;
            }
        }
        
        //public float JumpControlPower = 0.14f;

        [XmlIgnore]
        public Vector2 Movement = Vector2.Zero;

        [XmlIgnore]
        public bool IsOnGround = false;

        [XmlIgnore]
        public AABB OnPlatform = null;

        // Jumping state
        [XmlIgnore]
        public bool IsJumping;

        [XmlIgnore]
        private bool wasJumping;

        [XmlIgnore]
        private float jumpTime;

        [XmlIgnore]
        public Vector2 PreviousPosition;

        [XmlIgnore]
        public bool IsAlive
        {
            get;
            set;
        }

        [XmlIgnore]
        float _health = 1.0f;

        [XmlIgnore]
        public virtual float Health
        {
            get
            {
                return _health;
            }
            set
            {
                //If we've taken damage
                if(value < _health)
                    StunTimer = 0;

                _health = Math.Max(value, 0);

                if (_health <= 0)
                    IsAlive = false;
                else
                    IsAlive = true;
            }
        }

        public Actor()
        {
            Health = 1.0f;
            IsAlive = true;
            StunTimer = StunTime;
            StunBlinkRate = 1f;
        }

        /// <summary>
        /// Calculates the Y velocity accounting for jumping and
        /// animates accordingly.
        /// </summary>
        /// <remarks>
        /// During the accent of a jump, the Y velocity is completely
        /// overridden by a power curve. During the decent, gravity takes
        /// over. The jump velocity is controlled by the jumpTime field
        /// which measures time into the accent of the current jump.
        /// </remarks>
        /// <param name="velocityY">
        /// The player's current velocity along the Y axis.
        /// </param>
        /// <returns>
        /// A new Y velocity if beginning or continuing a jump.
        /// Otherwise, the existing Y velocity.
        /// </returns>
        private float DoJump(float velocityY, float dt)
        {
            // If the player wants to jump
            if (IsJumping)
            {
                // Begin or continue a jump
                if ((!wasJumping && IsOnGround) || jumpTime > 0.0f)
                {
                    //if (jumpTime == 0.0f)
                    //    jumpSound.Play();

                    jumpTime += dt;
                    //sprite.PlayAnimation(jumpAnimation);
                }

                // If we are in the ascent of the jump
                if (0.0f < jumpTime && jumpTime <= MaxJumpTime)
                {
                    // Fully override the vertical velocity with a power curve that gives players more control over the top of the jump
                    velocityY = JumpLaunchVelocity; //* (1.0f - (float)Math.Pow(jumpTime / MaxJumpTime, JumpControlPower));
                }
                else
                {
                    // Reached the apex of the jump
                    jumpTime = 0.0f;
                }
            }
            else
            {
                // Continues not jumping or cancels a jump in progress
                jumpTime = 0.0f;
            }
            wasJumping = IsJumping;

            return velocityY;
        }

        public override void OnCollide(CollisionEventArgs collision)
        {
            if (collision.Resolve)
            { 
                Position += collision.Translation;

                // If the collision stopped us from moving, reset the velocity to zero.
                if (collision.Direction == Direction.Left || collision.Direction == Direction.Right)
                    Velocity.X = 0;

                if (collision.Direction == Direction.Top || collision.Direction == Direction.Bottom)
                    Velocity.Y = 0;

                if (!(collision.Other is Actor))
                {
                    Animatable ani = collision.Other as Animatable;

                    if (collision.Direction == Direction.Bottom)
                    {
                        OnPlatform = collision.Other;
                        IsOnGround = true;
                    }
                    else if (collision.Direction == Direction.Top)
                    {
                        //Reset jump
                        jumpTime = MaxJumpTime;
                    }
                }
            }

            base.OnCollide(collision);
        }

        void UpdateStun(float dt)
        {
            StunTimer += dt;

            if (StunTimer < StunTime)
            {
                //Make actor blink
                this.CurrentAnimation.Tint = Color.White * (float)Math.Cos(StunTimer / StunTime * StunBlinkRate * MathHelper.TwoPi);
            }
            else
            {
                this.CurrentAnimation.Tint = Color.White;
            }
        }

        public override void Update(float dt)
        {
            UpdateStun(dt);

            base.Update(dt);

            if (!IsAlive)
                return;

            PreviousPosition = Position;

            // Base velocity is a combination of horizontal movement control and
            // acceleration downward due to gravity.

            Velocity += Movement * MoveAcceleration * dt;

            Velocity.Y = MathHelper.Clamp(Velocity.Y + GravityAcceleration * dt, -MaxFallSpeed, MaxFallSpeed);

            Velocity.Y = DoJump(Velocity.Y, dt);

            // Apply pseudo-drag horizontally.
            if (IsOnGround)
                Velocity.X *= GroundDragFactor;
            else
                Velocity.X *= AirDragFactor;

            // Prevent the player from running faster than his top speed.            
            Velocity.X = MathHelper.Clamp(Velocity.X, -MaxMoveSpeed, MaxMoveSpeed);

            if (OnPlatform != null)
            {
                if (Movement.Equals(Vector2.Zero)) //If we're idling on the platform, add its speed // || (this is NPC)
                    this.Position += OnPlatform.Velocity * dt;
            }

            // Apply velocity.
            this.Position += Velocity * dt;

            // Clear input.
            Movement = Vector2.Zero;
            IsJumping = false;
            IsOnGround = false;
            OnPlatform = null;
        }

    }
}
